#Importing the required libraries
import os
import pandas as pd
pd.core.common.is_list_like = pd.api.types.is_list_like
import pandas_datareader.data as web
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
from nsetools import Nse
from nsepy import get_history
import seaborn as sns
import plotly.express as px
from datetime import datetime, timedelta, date
The list of stocks of different indices have be taken from 'nseindia.com'
The data for 'P/E Ratio' is taken from 'valueresearchonline.com'
#Loading from excels
Bluechip = pd.read_excel('Data\\NIFTY50_PE_Ratio_19_Sept_2019.xlsx')
Midcap= pd.read_excel('Data\\Midcap100_PE_Ratio_19_Sept_2019.xlsx')
Smallcap = pd.read_excel('Data\\SmallCap100_PE_Ratio_19_Sept_2019.xlsx')
#Creating a new column identifying the stock type
Bluechip['Category'] = "Bluechip"
Midcap['Category'] = 'Midcap'
Smallcap['Category'] = 'Smallcap'
#Selecting stocks based on their index numbers
Bluechip_stocks = Bluechip[['Company', 'Symbol', 'Price', 'PE', 'Category']].iloc[[41, 24, 33, 18]]
Midcap_stocks = Midcap[['Company', 'Symbol', 'Price', 'PE', 'Category']].iloc[[42, 57, 25, 30]]
Smallcap_stocks = Smallcap[['Company', 'Symbol', 'Price', 'PE', 'Category']].iloc[[29, 7, 96, 19]]
#Merging all the 3 dataframes into 1
portfolio_stocks_list = pd.merge(Bluechip_stocks, Midcap_stocks, how = 'outer')
portfolio_stocks_list = pd.merge(portfolio_stocks_list, Smallcap_stocks, how = 'outer')
print(portfolio_stocks_list.shape)
#Final list of stock for portfolio
portfolio_stocks_list
#nse = Nse()
#Data Download
start = date(2016,6,6)
end = date(2019,7,2)
portfolio_data = []
for i in portfolio_stocks_list.Symbol.values:
portfolio_data.append(get_history(i, start = start, end = end))
portfolio_stocks_data = pd.concat(portfolio_data)
print(portfolio_stocks_data.shape)
portfolio_stocks_data.head()
#Verifying whether all the selected stocks have data for the specified period
portfolio_stocks_data['Symbol'].value_counts()
#Subsetting the required fields
portfolio_stocks_df = portfolio_stocks_data[['Symbol', 'Prev Close']]
#Renaming column to avoid spaces in the column header
portfolio_stocks_df.rename({'Prev Close' : 'Prev_close'},inplace = True)
#Using pivot to create stocks as columns
portfolio_stocks_df = portfolio_stocks_df.pivot(columns= 'Symbol', index= portfolio_stocks_data.index)
#Setting the columns
portfolio_stocks_df.columns = [col[1] for col in portfolio_stocks_df.columns]
#Filling 'NaN' if any
portfolio_stocks_df.fillna(0, inplace = True)
portfolio_stocks_df.head()
Now that we have clean data for 12 stocks from 3 different categories let's see the price movement for each stock
from pandas.plotting import register_matplotlib_converters
register_matplotlib_converters()
plt.figure(figsize= (14, 8))
for c in portfolio_stocks_df.columns:
plt.plot(portfolio_stocks_df.index, portfolio_stocks_df[c], label = c, lw = 3)
plt.legend(loc = 'best', fontsize = 12)
plt.ylabel('Price in INR')
It looks like HeroMotoCo and Venkeys have a relatively high which makes to difficult to understand the trend of other stocks.
#Calculating the percentage change for each stock
returns = portfolio_stocks_df.pct_change().dropna()
pd.set_option('display.max_rows', 800)
#Creating returns for each category to analyse the volatility easily
returns_bluechip = returns[['TATASTEEL', 'IBULHSGFIN', 'NTPC', 'HEROMOTOCO' ]]
returns_midcap = returns[['HEXAWARE', 'LICHSGFIN', 'EDELWEISS', 'FEDERALBNK' ]]
returns_smallcap = returns[['FLFL', 'BAJAJELEC', 'VENKEYS', 'CYIENT' ]]
#1. Plotting Bluechip Companies returns
plt.figure(figsize = (14, 8))
for c in returns_bluechip.columns.values:
plt.plot(returns_bluechip.index, returns_bluechip[c], lw = 2, label = c)
plt.legend(loc = 'upper right', fontsize = 12)
plt.ylabel('Percentage Change')
plt.title('Bluechip Stocks Returns Anaysis')
#2. Plotting Midcap Companies returns
plt.figure(figsize = (14, 8))
for c in returns_midcap.columns.values:
plt.plot(returns_midcap.index, returns_midcap[c], lw = 2, label = c)
plt.legend(loc = 'upper right', fontsize = 12)
plt.ylabel('Percentage Change')
plt.title('Midcap Stocks Returns Anaysis')
#3. Plotting Smallcap Companies returns
plt.figure(figsize = (14, 8))
for c in returns_smallcap.columns.values:
plt.plot(returns_smallcap.index, returns_smallcap[c], lw = 2, label = c)
plt.legend(loc = 'upper right', fontsize = 12)
plt.ylabel('Percentage Change')
plt.title('Smallcap Stocks Returns Anaysis')
From the above three graphs we can clearly see that Blue chip stocks have low standard deviation whereas the standard devialtion increases for Midcap and higher for Small cal stocks. So, at this point we can assume that if we make a high risk - high return portfolio the weights allocation could be higher for small cap stocks.
#Returns is calculated by percentage change of each stock
returns = portfolio_stocks_df.pct_change().dropna()
mean_returns = returns.mean()
mean_returns
#Creating the covariance matrix of returns
cov_matrix = returns.cov()
To assess a portfolio's performance I'll be using the basic Sharpe Ratio.
Sharpe Ratio can be calculated using the formula:
(Expected return - Risk free rate) / Portfolio standard deviation
def annual_portfolio_performance(weights, mean_returns, cov_matrix):
annual_returns = np.sum(mean_returns * weights) * 250
annual_risk = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) * np.sqrt(250)
return annual_returns, annual_risk
def random_portfolios(num_assets, num_portfolios, mean_returns, cov_matrix, risk_free_rate):
#creating a 3d array with length specied in 'num_portfolios'
results = np.zeros((3,num_portfolios))
#Generate random weights and create a list of all the random generated weights
weights_record = []
#For 'n' number of portfolios we are generating random weights
for i in range(num_portfolios):
weights = np.random.random(num_assets)
weights /= np.sum(weights)
#'weights_record' is the list of weights for a given number of portfolios and assets
weights_record.append(weights)
#Using the above created 'annual_portfolio_perfomance' function we generate portfolio standard deviation and returns
portfolio_return, portfolio_std_dev = annual_portfolio_performance(weights, mean_returns, cov_matrix)
# So for every set of weights we are calculating the risk, return and sharpe ration and saving it in results.
results[0,i] = portfolio_return
results[1,i] = portfolio_std_dev
results[2,i] = (portfolio_return - risk_free_rate) / portfolio_std_dev
return results, weights_record
#Defining the required values for the function 'random_portfolios' input
num_assets = portfolio_stocks_df.shape[1]
num_portfolios = 25000
# The risk free rate is taken from the govt bond annual return rate.
risk_free_rate = 0.069
Using the function 'random_portfolios' now we have 25000 randomly generated weights for 4 assets and their respective returns, portolio standard deviation (risk) and Sharpe ratio.
def efficient_frontier_random_portfolios(num_assets, num_portfolios, mean_returns, cov_matrix, risk_free_rate):
#Step 1
final_results, final_weights = random_portfolios(num_assets, num_portfolios,mean_returns, cov_matrix, risk_free_rate)
#Step 2
#Taking the maximum values of 'results[2]' which is the sharpe ratio
max_sharpe_indexId = np.argmax(final_results[2])
#Getting the returns and risk for max sharpe ratio
portfolio_return, portfolio_volatility = final_results[0, max_sharpe_indexId], final_results[1, max_sharpe_indexId]
#Creating a dataframe of the final weights of max sharpe ratio
max_sharpe_allocation = pd.DataFrame(final_weights[max_sharpe_indexId], index = portfolio_stocks_df.columns, columns = ['Weights_Allocation'])
#Rounding the values to 2 digits
max_sharpe_allocation.Weights_Allocation = [round(i*100, 2) for i in max_sharpe_allocation.Weights_Allocation]
#Transposing Stock names from index to columns
max_sharpe_allocation = max_sharpe_allocation.T
#Step 3
min_vol_indexId = np.argmin(final_results[1])
portfolio_min_return, portfolio_min_volatility = final_results[0, min_vol_indexId], final_results[1, min_vol_indexId]
min_vol_allocation = pd.DataFrame(final_weights[min_vol_indexId], index = portfolio_stocks_df.columns, columns = ['Allocation'])
min_vol_allocation.Allocation = [round(i*100, 2) for i in min_vol_allocation.Allocation]
min_vol_allocation = min_vol_allocation.T
#Step 4
print("_" * 125)
print("Maximum Sharpe Allocation \n")
print("Annual Portfolio Return", round(portfolio_return, 4))
print("Annual Risk / Volatility", round(portfolio_volatility, 4))
print("Maximum Sharpe Ratio", final_results[2, max_sharpe_indexId])
print("\n")
print(max_sharpe_allocation)
print("_" * 125)
print("Minimum Volatility Annual Portfolio Return")
print("Annual Return", round(portfolio_min_return, 2))
print("Annual Risk / Volatility", round(portfolio_min_volatility, 2))
print("Sharpe Ratio", final_results[2, min_vol_indexId])
print("\n")
print(min_vol_allocation)
#Step 5 - Visualization
plt.figure(figsize=(20, 12))
plt.scatter(final_results[1, :], final_results[0, :], c = final_results[2, :], cmap = 'YlGnBu', s = 10, alpha = 3)
plt.colorbar()
plt.scatter(portfolio_volatility, portfolio_return, marker = '*', color = 'r', s = 500, label = 'Maximum Sharpe Ratio')
plt.scatter(portfolio_min_volatility, portfolio_min_return, marker = '*', color = 'g', s = 500, label = 'Minimum Volatility')
plt.title("Simulation of Portfolio Optimization based on Efficient Frontier")
plt.xlabel("Annualised Volatility / Risk")
plt.ylabel("Annualised Returns")
plt.legend()
efficient_frontier_random_portfolios(num_assets, num_portfolios, mean_returns, cov_matrix, risk_free_rate)
from pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models
from pypfopt import expected_returns
This library uses the sharpe ratio as the objective function and optimises the weights as per the investment strategy
# Calculate expected returns and sample covariance
mu = expected_returns.mean_historical_return(portfolio_stocks_df) #Mean Returns
S = risk_models.sample_cov(portfolio_stocks_df) # Annualised covariance matrix
# Calculate expected returns and sample covariance
Mean_returns = expected_returns.mean_historical_return(portfolio_stocks_df) #Mean Returns
Cov_matrix = risk_models.sample_cov(portfolio_stocks_df) # Annualised covariance matrix
# Optimise for maximal Sharpe ratio
#The range for weights is given between '0' and '1' so that we don't have any negative allocation.
ef = EfficientFrontier(Mean_returns, Cov_matrix, weight_bounds=(0,1))
raw_weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
print(cleaned_weights)
ef.portfolio_performance(verbose=True)
cleaned_weights_MS= pd.DataFrame(list(cleaned_weights.items()), columns= ['Symbol', 'OPT_Max_Sharpe_Weight'])
#Rounding the weights
cleaned_weights_MS['OPT_Max_Sharpe_Weight'] = np.round(cleaned_weights_MS['OPT_Max_Sharpe_Weight'] * 100, 2)
#Merging with main dataframe for other stock details
cleaned_weights_MS = pd.merge(cleaned_weights_MS, portfolio_stocks_list, how = 'left')
#And finally sorting with "Category" column for better visualization analysis
cleaned_weights_MS = cleaned_weights_MS.sort_values('Category')
# Optimise for maximal Sharpe ratio
ef = EfficientFrontier(mu, S, weight_bounds=(0,1))
# The input for finding requried low risk are Expected risk and Risk Free Rate
# Here I've given the acceptable risk to 8% and risk free rate '6.9%'
raw_weights = ef.efficient_risk(0.08, 0.069)
cleaned_weights_low_risk = ef.clean_weights()
print(cleaned_weights_low_risk)
ef.portfolio_performance(verbose=True)
Although we have given the minimum risk acceptance level at 8% but with this current seletion of stocks 14.4% is the least risk possible.
cleaned_weights_low_risk= pd.DataFrame(list(cleaned_weights_low_risk.items()), columns= ['Symbol', 'OPT_Low_Risk_Weight'])
#Rounding the weights
cleaned_weights_low_risk['OPT_Low_Risk_Weight'] = np.round(cleaned_weights_low_risk['OPT_Low_Risk_Weight'] * 100, 2)
#Merging with main dataframe for other stock details
cleaned_weights_low_risk = pd.merge(cleaned_weights_low_risk, portfolio_stocks_list, how = 'left')
#And finally sorting with "Category" column for better visualization analysis
cleaned_weights_low_risk = cleaned_weights_low_risk.sort_values('Category')
fig1 = px.bar(cleaned_weights_MS, x = 'Symbol', y = 'OPT_Max_Sharpe_Weight',
color= 'Category',
title = 'High Risk - High Return Strategy (Max Sharpe Ratio)',
labels = {'Symbol': 'Stock', 'OPT_Max_Sharpe_Weight': 'Max Sharpe Weight'})
fig1.show()
fig2 = px.bar(cleaned_weights_low_risk, x = 'Symbol', y = 'OPT_Low_Risk_Weight',
color= 'Category',
title = 'Low Risk Strategy',
labels = {'Symbol': 'Stock', 'OPT_Low_Risk_Weight': 'Low Risk Weight'})
fig2.show()